package com.ironman.h6b1gw.rest;

import cn.hutool.json.JSONObject;
import com.alibaba.csp.sentinel.annotation.SentinelResource;
import com.alibaba.csp.sentinel.slots.block.BlockException;
import com.alibaba.csp.sentinel.slots.block.RuleConstant;
import com.alibaba.csp.sentinel.slots.block.authority.AuthorityRule;
import com.alibaba.csp.sentinel.slots.block.authority.AuthorityRuleManager;
import com.alibaba.csp.sentinel.slots.block.degrade.DegradeRule;
import com.alibaba.csp.sentinel.slots.block.degrade.DegradeRuleManager;
import com.alibaba.csp.sentinel.slots.block.degrade.circuitbreaker.CircuitBreaker;
import com.alibaba.csp.sentinel.slots.block.degrade.circuitbreaker.CircuitBreakerStrategy;
import com.alibaba.csp.sentinel.slots.block.degrade.circuitbreaker.EventObserverRegistry;
import com.alibaba.csp.sentinel.slots.block.flow.FlowException;
import com.alibaba.csp.sentinel.slots.block.flow.FlowRule;
import com.alibaba.csp.sentinel.slots.block.flow.FlowRuleManager;
import com.alibaba.csp.sentinel.util.TimeUtil;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.CrossOrigin;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;

import javax.annotation.PostConstruct;
import java.util.Collections;

@Slf4j
@CrossOrigin("*")
@RestController
public class LoginController {

    @PostConstruct
    public void init() {
        initFlowQpsRule();
        initDegradeRule();
        initWhiteRules();
        initBlackRules();
    }


    @PostMapping("login")
    @SentinelResource(value = "login", blockHandler = "exceptionHandler")
    public JSONObject login(@RequestParam String username, @RequestParam String password) {
        log.info("username: {}, password: {}", username, password);
        JSONObject res = new JSONObject();
        res.set("code", "admin".equals(username) ? 0 : 1);
        res.set("msg", null);
        return res;
    }

    public JSONObject exceptionHandler(String username, String password, BlockException ex) {
        log.info("block username: {}, password: {} {}", username, password, ex instanceof FlowException);
        JSONObject res = new JSONObject();
        res.set("code", 1);
        res.set("msg", null);
        return res;
    }

    private static void initFlowQpsRule() {
        FlowRule rule = new FlowRule();
        rule.setResource("login");
        rule.setCount(2);
        rule.setGrade(RuleConstant.FLOW_GRADE_QPS);
        rule.setLimitApp("default");
        rule.setControlBehavior(RuleConstant.CONTROL_BEHAVIOR_DEFAULT);
        FlowRuleManager.loadRules(Collections.singletonList(rule));
    }

    private static void initDegradeRule() {
        DegradeRule rule = new DegradeRule("login")
                .setGrade(CircuitBreakerStrategy.SLOW_REQUEST_RATIO.getType())
                // Max allowed response time
                .setCount(50)
                // Retry timeout (in second)
                .setTimeWindow(10)
                // Circuit breaker opens when slow request ratio > 60%
                .setSlowRatioThreshold(0.6)
                .setMinRequestAmount(100)
                .setStatIntervalMs(20000);

        DegradeRuleManager.loadRules(Collections.singletonList(rule));

        EventObserverRegistry.getInstance()
                .addStateChangeObserver("logging",
                        (prevState, newState, degradeRule, snapshotValue) -> {
                            if (newState == CircuitBreaker.State.OPEN) {
                                // 变换至 OPEN state 时会携带触发时的值
                                System.err.printf("%s -> OPEN at %d, snapshotValue=%.2f%n", prevState.name(), TimeUtil.currentTimeMillis(), snapshotValue);
                            } else {
                                System.err.printf("%s -> %s at %d%n", prevState.name(), newState.name(), TimeUtil.currentTimeMillis());
                            }
                        });
    }

    private static void initWhiteRules() {
        AuthorityRule rule = new AuthorityRule();
        rule.setResource("login");
        rule.setStrategy(RuleConstant.AUTHORITY_WHITE);
        rule.setLimitApp("appA,appE");
        AuthorityRuleManager.loadRules(Collections.singletonList(rule));
    }

    private static void initBlackRules() {
        AuthorityRule rule = new AuthorityRule();
        rule.setResource("login");
        rule.setStrategy(RuleConstant.AUTHORITY_BLACK);
        rule.setLimitApp("appA,appB");
        AuthorityRuleManager.loadRules(Collections.singletonList(rule));
    }
}
